package com.letoken.platform.repo.utlis;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Slf4j
public class LocalBlockUtils {

//    static Logger log = LoggerFactory.getLogger(LocalBlockUtils.class);

    private static final String WALLET_HOST = "wallet.host";
    private static String url ;
    private static HttpHeaders JSON_HEADERS;
    private static RestTemplate restTemplate = new RestTemplate();

    private static StringRedisTemplate redisTemplate;

    private static final String APP_ADDRESS_KEY = "APP:ADDRESS:%s_%s";



    static {
        Environment e = SpringUtil.getBean(Environment.class);
        url = e.getProperty(WALLET_HOST);
        if (!StringUtils.hasLength(url)){
            System.exit(10001);
        }
        redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
//        url = "http://192.168.0.35:8080";
        JSON_HEADERS = new HttpHeaders();
        JSON_HEADERS.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8"));

        PoolingHttpClientConnectionManager connectionPoolManager = new PoolingHttpClientConnectionManager();
        connectionPoolManager.setMaxTotal(50);//最大连接数
        connectionPoolManager.setDefaultMaxPerRoute(30);//每个路由默认接收的最大连接数
        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connectionPoolManager).build();
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setHttpClient(httpClient);
        httpRequestFactory.setConnectionRequestTimeout(3000);//获取链接超时时间
        httpRequestFactory.setConnectTimeout(5000);// 指客户端和服务器建立连接的timeout
        httpRequestFactory.setReadTimeout(60000);// socketTimeout 读取数据的超时时间
        restTemplate.setRequestFactory(httpRequestFactory);





    }


    public static void main(String[] args) {
        BaseResp<String> a = withdrawApplyList(WithDrawApplyListDto.builder()
                .coinId(1)
                .pageId(1)
                .pageSize(10)
                .build());
        System.out.println(JSON.toJSONString(a));
    }




    public static BaseResp<String> withdrawAudit(WithDrawAuditDto dto){
        BaseResp<String> resp =  post(String.format("%s%s",url,"/withdraw/approve"),dto);
        return resp;
    }

    public static BaseResp<String> withdrawAuditCAP(WithDrawAuditDto dto){
        BaseResp<String> resp =  post(String.format("%s%s",url,"/withdraw/approveCap"),dto);
        return resp;
    }


    public static BaseResp<String> withdrawApplyList(WithDrawApplyListDto dto){
        BaseResp<String> resp =  post(String.format("%s%s",url,"/withdraw/list"),dto);
        if (!resp.isSuccess())
            throw new RuntimeException("申请列表查询失败!" + resp);
        return resp;
    }




    public static AddressPo obtainUserId(ObtainUserIdDto dto){
        log.info("进入通过用户ID获取地址接口:{}",JSON.toJSONString(dto));
        AddressPo po = new AddressPo();
        if (ObjectUtils.isEmpty(dto.getCoinId()) || dto.getCoinId() <= 0){
            throw new RuntimeException("根据地址获取用户ID失败! coinId 错误" );
        }
        if (!StringUtils.hasLength(dto.getAddr())){
            throw new RuntimeException("根据地址获取用户ID失败! 地址 错误" );
        }
        dto.setSysId(1);
        po.setAddr(dto.getAddr());
        String name = String.format(APP_ADDRESS_KEY,dto.getSysId(),dto.getCoinId());
        final String strUserId = (String) redisTemplate.opsForHash().get(name,dto.getAddr());
        if (StringUtils.hasLength(strUserId)){
            po.setUserId(Integer.valueOf(strUserId));
            po.success = true;
        }else {
            po.setSuccess(false);
        }
        log.info("地址信息信息：{} {}",JSON.toJSONString(dto),JSON.toJSONString(po));
        if (po.isSuccess()){
            return po;
        }
        BaseResp<Integer> resp =  get(String.format("%s%s?address=%s",url,"/wallet/getUserId",dto.getAddr()));
        if (resp.isSuccess() && !ObjectUtils.isEmpty(resp.getData()) ){
            po.success = false;
            if (resp.getData() > 0){
                log.info("获取地址响应结果xx:{}",resp);
                po.setUserId(resp.getData());
                po.success = true;
//                String key = String.valueOf(resp.getData());
//                redisTemplate.opsForHash().put(name,key,dto.getAddr());
//                redisTemplate.opsForHash().put(name,dto.getAddr(),key);
            }
            log.info("获取地址响应结果xxx:{} {}",JSON.toJSONString(dto),JSON.toJSONString(resp));
            return po;
        }
        throw new RuntimeException("根据地址获取用户ID失败!" + JSON.toJSONString(resp));
    }





    /**
     * 生成或者获取一个一个地址
     * @param dto
     * @return
     */
    public static AddressPo obtainAddress(ObtainAddressDto dto){
        AddressPo po = new AddressPo();
        try {
            if (ObjectUtils.isEmpty(dto.getCoinId()) || dto.getCoinId() <= 0){
                throw new RuntimeException("创建地址失败! coinId 错误" );
            }
            dto.setSysId(1);
            String name = String.format(APP_ADDRESS_KEY,dto.getSysId(),dto.getCoinId());


//            // 根据地址获取用户ID
//            if (StringUtils.hasLength(dto.getAddr())){
//                po.setAddr(dto.getAddr());
//                final String strUserId = (String) redisTemplate.opsForHash().get(name,dto.getAddr());
//                if (StringUtils.hasLength(strUserId)){
//                    po.setUserId(Integer.valueOf(strUserId));
//                    po.success = true;
//                }else {
//                    po.success = false;
//                }
//                return po;
//            }

            if (ObjectUtils.isEmpty(dto.getUserId()) || dto.getUserId() <= 0){
                throw new RuntimeException("创建地址失败! userId 错误" );
            }
            String key = String.valueOf(dto.getUserId());
            if (redisTemplate.opsForHash().hasKey(name,key)){
                po.setUserId(dto.userId);

                po.setAddr((String) redisTemplate.opsForHash().get(name,key));
                po.success = true;
                return po;
            }
            BaseResp<String> resp =  post(String.format("%s%s?userId=%s&coinId=%s&sysId=%s",url,"/wallet/address",dto.getUserId(),dto.getCoinId(),dto.getSysId()),null);
            if (resp.isSuccess()) {
                //resp
                redisTemplate.opsForHash().put(name,key,resp.getData());
                redisTemplate.opsForHash().put(name,resp.getData(),key);
                po.setUserId(dto.userId);
                po.setAddr(resp.getData());
                po.success = true;
                return po;
            }

            throw new RuntimeException("创建地址失败!" + resp);
        } finally {
            log.info("获取地址响应结果:{}",po);
        }
    }


    /**
     * 充值确认或者说回调
     * @param dto
     * @return
     */
    public static BaseResp<String> rechargeConfirm(RechargeConfirmAck dto){
        return  post(String.format("%s%s",url,"/recharge/confirm"),dto);
    }


    /**
     * 申请提币
     * @param dto
     * @return
     */
    public static void withdrawApply(WithDrawApplyDto dto){
        BaseResp resp =  post(String.format("%s%s",url,"/withdraw/apply"),dto);
        if (!resp.isSuccess())
            throw new RuntimeException("申请提现失败!" + resp);
    }



    /**
     * 初始化提现钱包 不需要调用
     * @param dto
     * @return
     */
    @Deprecated
    public static BaseResp<String> withdrawInitWallet(InitWalletDto dto){
        return  post(String.format("%s%s",url,"/withdraw/init/wallet"),dto);
    }




    /**
     * 手动确认入账，一般不需要调用
     * @param dto
     * @return
     */
    @Deprecated
    public static Boolean manualConfirm(ManualConfirmDto dto){
        BaseResp resp = post(String.format("%s%s?txId=%s&coinId=%s",url,"/recharge/manual",dto.getTxId(),dto.getCoinId() ),null);
        return resp.isSuccess();
    }






    private static <T> BaseResp<T> get(String url){
        ResponseEntity<BaseResp<T>> re = null;
        try {
            HttpEntity<String> httpEntity = new HttpEntity<>(null, JSON_HEADERS);
            ParameterizedTypeReference<BaseResp<T>> typeReference = new ParameterizedTypeReference<BaseResp<T>>() {};
            re  = restTemplate.exchange(url, HttpMethod.GET,httpEntity, typeReference);
            if (re.getStatusCodeValue() == HttpStatus.OK.value()){
                BaseResp<T> resp = re.getBody();
                if (!ObjectUtils.isEmpty(resp) && resp.isSuccess()){
                    return resp;
                }
            }
            log.error("调用钱包服务响应错误,{},{}",url,JSON.toJSONString(re));
        } catch (RestClientException e) {
            log.error("调用钱包服务出现异常,{},{}",url,JSON.toJSONString(re),e);
        }
        return BaseResp.FAIL;


    }



    private static  <T> BaseResp<T> post(String url, Object o ){
        String p = null;
        ResponseEntity<BaseResp<T>> re = null;
        try {
            p = JSON.toJSONString(o);
            HttpEntity<String> httpEntity = new HttpEntity<>(p, JSON_HEADERS);
            ParameterizedTypeReference<BaseResp<T>> typeReference = new ParameterizedTypeReference<BaseResp<T>>() {};
            re  = restTemplate.exchange(url, HttpMethod.POST,httpEntity, typeReference);
            if (re.getStatusCodeValue() == HttpStatus.OK.value()){
                BaseResp<T> resp = re.getBody();
                if (!ObjectUtils.isEmpty(resp) && resp.isSuccess()){
                    return resp;
                }
            }
            log.error("调用钱包服务响应错误,{},{}",p,JSON.toJSONString(re));
        } catch (RestClientException e) {
            log.error("调用钱包服务出现异常,{},{}",p,JSON.toJSONString(re),e);
        }
        return BaseResp.FAIL;
    }





    // 手动确认
    @Data
    @Builder
    public static class ManualConfirmDto implements Serializable{
        private Integer coinId;
        private String txId;
    }

    // 业务处理回调
    @Data
    @Builder
    public static class RechargeConfirmAck implements Serializable{
        private Integer id;
//        private String seqNo;
        private Integer userId;
//        private Integer coinId;
        private String txId;
//        private String fromAddr;
//        private String toAddr;
//        // 默认0 已广播1 业务确认2
//        private Byte status;
        private BigDecimal amount;
//        private String blockHash;
//        private Integer blockNumber;
    }


    // 初始化钱包
    @Data
    public static class InitWalletDto implements Serializable{
        private Integer coinId;
    }

    // 绑定钱包地址
    @Data
    @Builder
    public static class ObtainAddressDto implements Serializable{
        private Integer userId;
        private Integer coinId;
        private Integer sysId;
        private String addr;
    }

    @Data
    @Builder
    public static class ObtainUserIdDto implements Serializable{
        private Integer coinId;
        private Integer sysId;
        private String addr;
    }


    @Data
    @Builder
    public static class WithDrawAuditDto implements Serializable{
        private Integer applyId;
        private String remark;
        private String from;
        private Integer status;
        private String hash;
        private Integer operatorId;
        private String seqNo;
    }




    @Data
    @Builder
    public static class WithDrawApplyListDto implements Serializable{
        private Integer pageId;
        private Integer pageSize;
        private Integer coinId;
        private String fromAddr;
        private String toAddr;
        private Integer status;
        private Integer userId;
        private BigDecimal minAmount;
        private BigDecimal maxAmount;
    }



    @Data
    public static class AddressPo implements Serializable{
        private String addr;
        private Integer userId;


        private Boolean success = false;

        public Boolean isSuccess(){
            return success;
        }

        @Override
        public String toString(){
            return JSON.toJSONString(this);
        }
    }




    // 提现申请
    @Data
    @Builder
    public static class WithDrawApplyDto implements Serializable{
        private String seqNo;
        private Integer userId;
        private Integer coinId;
        private String toAddr;
        private BigDecimal amount;
        private String remark;
    }



    @Data
    public static class BaseResp<T> implements Serializable{
        private final static Integer SUCCESS_CODE = 80000000;
        private final static Integer FAIL_CODE = -1;

        private Integer code;
        private T data;
        private String msg;
        private Long timeStamp;


        public static BaseResp FAIL = new BaseResp(FAIL_CODE);

        public BaseResp(Integer code) {
            this.code = code;
        }

        public BaseResp() {
        }

        public BaseResp(Integer code, T data, String msg, Long timeStamp) {
            this.code = code;
            this.data = data;
            this.msg = msg;
            this.timeStamp = timeStamp;
        }




        public Boolean isSuccess(){
            return !ObjectUtils.isEmpty(code) && code.equals(SUCCESS_CODE);
        }

        @Override
        public String toString(){
            return JSON.toJSONString(this);
        }

    }






}
